#include <webx/menu.h>
#include <webx/route.h>

class InitSystem : public webx::ProcessBase
{
protected:
	int process();
};

HTTP_WEBCGI(CGI_PRIVATE, "@null")
DEFINE_HTTP_CGI_EXPORT_FUNC(InitSystem)

static HttpServer* app = HttpServer::Instance();

#ifdef XG_WITHPYTHON

#include <python/PythonLoader.h>

static string pypath;
static PythonLoader py;

static BOOL PythonSetup(BOOL lock)
{
	bool inited = false;

	CHECK_FALSE_RETURN(py.canUse());

	if (lock)
	{
		PythonLocker lk;

		py.call("setup", ParamVector(app->getPath()), inited);
	}
	else
	{
		py.call("setup", ParamVector(app->getPath()), inited);
	}

	return inited;
}

#endif

EXTERN_DLL_FUNC void SetLogFlag(int flag)
{
	LogThread::Instance()->setLogFlag(flag);
}
EXTERN_DLL_FUNC int Loop(const char* path)
{
	const char* cmd[] = {"webserver"};

	if (path == NULL) return XG_PARAMERR;

	Process::Instance(ARR_LEN(cmd), (char**)(cmd));

	if (app->init(path)) app->loop();

	return XG_SYSERR;
}
EXTERN_DLL_FUNC void WriteLog(const char* tag, const char* msg)
{
	if (tag && msg)
	{
		int level = eINF;
		char flag = toupper(tag[0]);
		
		switch(flag)
		{
		case 'D':
			level = eDBG;
			break;
		case 'T':
			level = eTIP;
			break;
		case 'E':
			level = eERR;
			break;
		case 'I':
			if (toupper(tag[1]) == 'M') level = eIMP;
			break;
		}

		LogTrace(level, msg);
	}
}

EXTERN_DLL_FUNC int GetId()
{
	return app->getId();
}

EXTERN_DLL_FUNC int GetPort()
{
	return app->getPort();
}
EXTERN_DLL_FUNC const char* GetHost()
{
	return app->getHost().c_str();
}
EXTERN_DLL_FUNC const char* GetSequence()
{
	thread_local string val;

	val = app->getSequence();

	return val.c_str();
}
EXTERN_DLL_FUNC const char* GetConfig(const char* key)
{
	thread_local string val;

	val = app->getConfigFile()->getVariable(key);

	return val.c_str();
}
EXTERN_DLL_FUNC const char* GetMimeType(const char* key)
{
	thread_local string val;

	val = app->getMimeType(key);

	return val.c_str();
}
EXTERN_DLL_FUNC const char* GetParameter(const char* key)
{
	thread_local string val;

	val = webx::GetParameter(key);

	return val.c_str();
}
EXTERN_DLL_FUNC const char* GetRouteHost(const char* path)
{
	thread_local string val;
	HostItem item = webx::GetRouteHost(path);

	if (item.host.empty()) return NULL;

	val = item.toString();

	return val.c_str();
}
EXTERN_DLL_FUNC const char* GetConfileContent(const char* title)
{
	thread_local string val;

	val = webx::GetConfileContent(title);

	return val.c_str();
}

EXTERN_DLL_FUNC int DisableSession(const char* sid)
{
	try
	{
		sp<Session> session = webx::GetSession(sid);

		return session ? XG_OK : XG_TIMEOUT;
	}
	catch (Exception e)
	{
	}

	return  XG_SYSERR;
}
EXTERN_DLL_FUNC int CreateSession(const char* sid, int timeout)
{
	try
	{
		sp<Session> session = webx::GetSession(sid, timeout);

		if (session) return XG_OK;
	}
	catch (Exception e)
	{
	}

	return XG_SYSERR;
}
EXTERN_DLL_FUNC void SetCgiAccess(const char* path, const char* access)
{
	if (strcasecmp(access, "public") == 0)
	{
		app->setCgiAccess(path, CGI_PUBLIC);
	}
	else if (strcasecmp(access, "protect") == 0)
	{
		app->setCgiAccess(path, CGI_PROTECT);
	}
	else if (strcasecmp(access, "private") == 0)
	{
		app->setCgiAccess(path, CGI_PRIVATE);
	}
	else
	{
		app->setCgiAccess(path, CGI_DISABLE);
	}
}
EXTERN_DLL_FUNC void SetCgiExtdata(const char* path, const char* extdata)
{
	app->setCgiExtdata(path, extdata ? extdata : "");
}
EXTERN_DLL_FUNC void SetCgiDoc(const char* path, const char* reqdoc, const char* rspdoc, const char* remark)
{
	app->setCgiDoc(path, reqdoc, rspdoc, remark);
}
EXTERN_DLL_FUNC const char* GetSession(const char* sid, const char* key)
{
	thread_local string val;

	try
	{
		sp<Session> session = webx::GetSession(sid);

		if (!session) return NULL;

		val = session->get(key);
	}
	catch (Exception e)
	{
	}

	return val.c_str();
}
EXTERN_DLL_FUNC int SetSession(const char* sid, const char* key, const char* val)
{
	if (val == NULL) val = "";

	try
	{
		sp<Session> session = webx::GetSession(sid);

		if (!session) return XG_TIMEOUT;

		if (session->set(key, val)) return XG_OK;
	}
	catch (Exception e)
	{
	}

	return XG_SYSERR;
}
EXTERN_DLL_FUNC int GetLastRemoteStatus()
{
	return webx::GetLastRemoteStatus();
}
EXTERN_DLL_FUNC const char* GetRemoteResult(const char* path, const char* param, const char* contype, const char* cookie)
{
	thread_local SmartBuffer data;
	
	if (param == NULL) param = "";
	if (cookie == NULL) cookie = "";
	if (contype == NULL) contype = "";

	data = webx::GetRemoteResult(path, param, contype, cookie);

	return data.str();
}

#ifdef XG_WITHJAVA

#include <java/JavaLoader.h>

#define cppstr(str) JavaLoader::CppString(env, str)
#define javastr(str) JavaLoader::JavaString(env, str)

static string jvmpath;
static JavaLoader jvm;
static string jvmclasspath;
static map<string, string> jvmcgimap;

static BOOL JavaSetup(BOOL lock)
{
	string msg;
	HttpDataNode node;

	CHECK_FALSE_RETURN(jvm.canUse());

	node.setValue("path", app->getPath());
	node.setValue("host", app->getHost());
	node.setValue("port", stdx::str(app->getPort()));
	node.setValue("config", app->getConfigFile()->getFilePath());

	msg = "\r\n\r\n" + node.toString();

	jvm.call("webx/Startup", "process", SmartBuffer(msg), true);

	return TRUE;
}

EXTERN_DLL_FUNC jint JNICALL Java_webx_WebApp_GetPort(JNIEnv* env, jclass obj)
{
	return app->getPort();
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetId(JNIEnv* env, jclass obj)
{
	return javastr(app->getPath());
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetHost(JNIEnv* env, jclass obj)
{
	return javastr(app->getHost());
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetPath(JNIEnv* env, jclass obj)
{
	return javastr(app->getPath());
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetSequence(JNIEnv* env, jclass obj)
{
	return javastr(app->getSequence());
}
EXTERN_DLL_FUNC void JNICALL Java_webx_WebApp_Loop(JNIEnv* env, jclass obj, jstring path)
{
	Loop(cppstr(path).c_str());
}
EXTERN_DLL_FUNC void JNICALL Java_webx_WebApp_SetLogFlag(JNIEnv* env, jclass obj, jint flag)
{
	LogThread::Instance()->setLogFlag(flag);
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetConfig(JNIEnv* env, jclass obj, jstring key)
{
	return javastr(app->getConfigFile()->getVariable(cppstr(key)));
}
EXTERN_DLL_FUNC void JNICALL Java_webx_WebApp_SetClassPath(JNIEnv* env, jclass obj, jstring path)
{
	jvmclasspath = stdx::replace(cppstr(path), "\\", "/");
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetMimeType(JNIEnv* env, jclass obj, jstring key)
{
	return javastr(app->getMimeType(cppstr(key)));
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetParameter(JNIEnv* env, jclass obj, jstring key)
{
	return javastr(webx::GetParameter(cppstr(key)));
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetRouteHost(JNIEnv* env, jclass obj, jstring path)
{
	HostItem item = webx::GetRouteHost(cppstr(path));

	if (item.host.empty()) return NULL;

	return javastr(item.toString());
}
EXTERN_DLL_FUNC void JNICALL Java_webx_WebApp_Trace(JNIEnv* env, jclass obj, jint level, jstring msg)
{
	LogTrace(level, cppstr(msg).c_str());
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetConfileContent(JNIEnv* env, jclass obj, jstring title)
{
	return javastr(webx::GetConfileContent(cppstr(title)));
}
EXTERN_DLL_FUNC void JNICALL Java_webx_WebApp_SetCgiAccess(JNIEnv* env, jclass obj, jstring path, jstring access)
{
	SetCgiAccess(cppstr(path).c_str(), cppstr(access).c_str());
}
EXTERN_DLL_FUNC void JNICALL Java_webx_WebApp_SetCgiExtdata(JNIEnv* env, jclass obj, jstring path, jstring extdata)
{
	SetCgiExtdata(cppstr(path).c_str(), cppstr(extdata).c_str());
}
EXTERN_DLL_FUNC void JNICALL Java_webx_WebApp_SetCgiDoc(JNIEnv* env, jclass obj, jstring path, jstring reqdoc, jstring rspdoc, jstring remark)
{
	SetCgiDoc(cppstr(path).c_str(), cppstr(reqdoc).c_str(), cppstr(rspdoc).c_str(), cppstr(remark).c_str());
}
EXTERN_DLL_FUNC jint JNICALL Java_webx_WebApp_GetLastRemoteStatus(JNIEnv* env, jclass obj)
{
	return webx::GetLastRemoteStatus();
}
EXTERN_DLL_FUNC jstring JNICALL Java_webx_WebApp_GetRemoteResult(JNIEnv* env, jclass obj, jstring path, jstring param, jstring contype, jstring cookie)
{
	SmartBuffer data = webx::GetRemoteResult(cppstr(path), cppstr(param), cppstr(contype), cppstr(cookie));

	if (data.isNull()) return NULL;

	return javastr(data.str());
}

#endif

class ReloadWorkItem : public WorkItem
{
public:
	void run()
	{
#ifdef XG_WITHJAVA
		JavaSetup(true);
#endif

#ifdef XG_WITHPYTHON
		PythonSetup(true);
#endif
	}
};

static void ReloadSystemConfig()
{
	stdx::async(newsp<ReloadWorkItem>());

	Sleep(100);
}

int InitSystem::process()
{
	int res = 0;
	static int inited = 0;
	static ConfigFile* cfg = app->getConfigFile();

#ifdef XG_LINUX
	string envspliter = ":";
#else
	string envspliter = ";";
#endif

#ifdef XG_WITHJAVA
	if (request && response)
	{
		if (jvm.canUse())
		{
			string key;
			string url = request->getPath();
			auto it = jvmcgimap.find(stdx::tolower(key = url));

			if (it != jvmcgimap.end())
			{
				string head = request->getHeadString();
				string data = request->getDataString();
				SmartBuffer buffer(head.length() + data.length() + 4);

				memcpy(buffer.str(), head.c_str(), head.length());
				memcpy(buffer.str() + head.length(), "\r\n\r\n", 4);
				memcpy(buffer.str() + head.length() + 4, data.c_str(), data.length());

				buffer = jvm.call(it->second, "process", buffer, true, &res);

				if (buffer.isNull()) return XG_SYSERR;

				if (res < 0) return res;

				char* end = buffer.str() + buffer.size();
				char* str = strstr(buffer.str(), "\r\n\r\n");

				if (str)
				{
					int len = 0;
					HttpHeadNode hdr;

					if (hdr.parse(string(buffer.str(), str)))
					{
						const map<string, string>& datmap = hdr.getDataMap();
						
						for (auto& item : datmap) response->setHeadValue(item.first, item.second);
					}

					str += 4;

					if ((len = end - str) <= 0) return XG_FAIL;

					memmove(buffer.str(), str, len);
					buffer.truncate(len);
				}

				return createFile(buffer.size()) ? file->write(buffer.str(), buffer.size()) : XG_SYSERR;
			}
		}
	}
	else
	{
		auto reload = [&](){
			string url;

			for (auto item : jvmcgimap) app->addCgiData(url = item.first);
			
			LogTrace(eINF, "load java cgimap success");
		};

		if (jvm.canUse())
		{
			reload();
		}
		else
		{
			cfg->getVariable("JAVA_CLASSPATH", jvmpath);
			
			if (jvmpath.length() > 0 || jvmclasspath.length() > 0)
			{
				string jarhome;
				string javahome;
				set<string> classvec;
				vector<string> pathvec;

				if (jvmclasspath.length() > 0) jvmpath += envspliter + jvmclasspath;
				
				jvmpath = stdx::replace(jvmpath, "\\", "/");
				jvmpath = stdx::replace(jvmpath, "//", "/");
				jvmpath = stdx::trim(jvmpath, envspliter);
				stdx::split(pathvec, jvmpath, envspliter);

				javahome = Process::GetEnv("JAVA_HOME");
				javahome = stdx::replace(javahome, "\\", "/");
				javahome = stdx::replace(javahome, "//", "/");

				jarhome = stdx::translate("$SOURCE_HOME/product/jar");
				jarhome = stdx::replace(jarhome, "\\", "/");
				jarhome = stdx::replace(jarhome, "//", "/");

				for (auto& jvmpath : pathvec)
				{
					size_t pos;
					vector<string> vec;

					if (jvmpath.find(jarhome) == 0 || jvmpath.find(javahome) == 0) continue;
					
					if (stdx::FindFile(vec, jvmpath, "*.class") > 0)
					{
						if (jvmpath.back() == '/' || jvmpath.back() == '\\')
						{
							pos = jvmpath.length();
						}
						else
						{
							pos = jvmpath.length() + 1;
						}
						
						for (auto& item : vec)
						{
							classvec.insert(item.substr(pos));
						}
					}
					else
					{
						SmartBuffer data = proc::exec("jar", "tvf", jvmpath);

						if (data.isNull())
						{
							LogTrace(eERR, "analysis package[%s] failed", jvmpath.c_str());
						}
						else
						{
							stdx::split(vec, data.str(), "\n");
							
							for (auto& item : vec)
							{
								if ((pos = item.rfind(' ')) == string::npos) continue;
								
								item = item.substr(pos + 1);
								
								if (item.find(".class") == string::npos) continue;

								classvec.insert(item);
							}
						}
					}
				}

				if (classvec.size() > 0)
				{
					vector<string> vec;

					stdx::FindFile(vec, stdx::translate("$SOURCE_HOME/product/jar"), "*.jar");

					for (auto& item : vec) jvmpath += envspliter + item;

					jvmpath += envspliter + Process::GetEnv("CLASSPATH");

					if (jvm.init(jvmpath) && JavaSetup(false))
					{
						string url;
						string key;
						SmartBuffer data;

						for (auto& item : classvec)
						{
							url = string(item.c_str(), item.c_str() + item.rfind('.'));

							data = jvm.call("webx/Startup", "getCgiPath", SmartBuffer(url), true, &res);

							if (data.isNull()) continue;

							key = HttpServer::GetWebAppPath(data.str(), url.c_str());

							if (key.empty()) continue;

							jvmcgimap[stdx::tolower(key)] = url;

							LogTrace(eINF, "load class[%s][%s] success", key.c_str(), url.c_str());
						}

						LogTrace(eINF, "initialize java module success");

						reload();
					}
					else
					{
						LogTrace(eERR, "import java module failed");
					}
				}
			}
		}
	}
#endif

#ifdef XG_WITHPYTHON
	if (request && response)
	{
		if (py.canUse())
		{
			int val = 0;
			PythonLocker lk;
			PythonLoader rs;

			string url = request->getPath();
			string head = request->getHeadString();
			string data = request->getDataString();

			if (py.call("process", ParamVector(url, head, data), rs))
			{
				int sz = rs.size();
				SmartBuffer buffer;

				if (sz < 0)
				{
					if (rs.isNumber()) return rs.getInteger();

					buffer = rs.getBuffer();
				}
				else if (sz > 0)
				{
					if (rs.getItem(0, buffer))
					{
						if (buffer.isNull()) buffer = rs.getItem(0);

						if (sz > 1)
						{
							PythonLoader hdr;
							vector<string> vec;
							
							if (rs.getItem(1, hdr) && hdr.getKeys(vec) > 0)
							{
								for (auto& key : vec) response->setHeadValue(key, hdr.getItem(key));
							}
						}
					}
					else
					{
						return XG_SYSERR;
					}
				}

				if (buffer.size() > 0) return createFile(buffer.size()) ? file->write(buffer.str(), buffer.size()) : XG_SYSERR;
			}
		}
	}
	else
	{
		auto reload = [&](){
			PythonLoader rs;
			vector<string> vec;

			if (py.call("getcgimap", ParamVector(), rs) && rs.getKeys(vec) > 0)
			{
				for (auto& key : vec) app->addCgiData(key);

				LogTrace(eINF, "load python cgimap success");
			}
			else
			{
				LogTrace(eERR, "load python cgimap failed");
			}
		};

		if (py.canUse())
		{
			PythonLocker lk;

			reload();
		}
		else
		{
			cfg->getVariable("PYTHON_CONFIGFILE_PATH", pypath);
			
			if (pypath.length() > 0)
			{
#ifdef XG_LINUX
				string libpath;
				vector<string> vec;

				libpath = stdx::translate("$SOURCE_HOME/product/lib/python/lib");

				if (stdx::FindFile(vec, libpath, "libpython*.so*") > 0) dlopen(vec[0].c_str(), RTLD_LAZY | RTLD_GLOBAL);
#endif
				PythonLoader::Setup(true);

				PythonLocker lock;

				if (py.import(pypath))
				{
					if (PythonSetup(false))
					{
						LogTrace(eINF, "initialize python module success");

						reload();
					}
					else
					{
						LogTrace(eERR, "initialize python module failed");
					}
				}
				else
				{
					LogTrace(eERR, "import python module failed");
				}
			}
		}
	}
#endif

	if (request && response) return XG_NOTFOUND;

	if (inited > 0) return XG_OK;

	string path;
	vector<string> vec;

	path = app->getPath() + "etc/plugin/bin/";

	stdx::FindFile(vec, path, "*.so");

	for (auto& item : vec) item = item.substr(path.length());

	std::sort(vec.begin(), vec.end(), [](const string& a, const string& b){
		if (a == "MenuModule.so" || a == "MenuModule.dll") return true;
		if (b == "MenuModule.so" || b == "MenuModule.dll") return false;
		return a > b;
	});

	for (auto& item : vec)
	{
		if (item == "InitSystem.so" || item == "InitSystem.dll") continue;

		webx::LoadHttpPlugin(path + item);
	}

	Process::SetObject("HTTP_RELOAD_SYSTEM_CONFIG_FUNC", (void*)ReloadSystemConfig);

	path = Process::GetEnv("PRODUCT_HOME");

	if (path.length() > 0)
	{
		Process::RegisterLibraryPath(path + "/lib/openssl/lib");
		Process::RegisterLibraryPath(path + "/lib/mysql/lib");
	}

	inited = XG_OK;

	return XG_OK;
}